// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/widget/desktop_aura/desktop_drop_target_win.h"

#include "base/metrics/histogram.h"
#include "base/win/win_util.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
#include "ui/events/event_constants.h"
#include "ui/wm/public/drag_drop_client.h"
#include "ui/wm/public/drag_drop_delegate.h"

using aura::client::DragDropClient;
using aura::client::DragDropDelegate;
using ui::OSExchangeData;
using ui::OSExchangeDataProviderWin;

namespace {

int ConvertKeyStateToAuraEventFlags(DWORD key_state)
{
    int flags = 0;

    if (key_state & MK_CONTROL)
        flags |= ui::EF_CONTROL_DOWN;
    if (key_state & MK_ALT)
        flags |= ui::EF_ALT_DOWN;
    if (key_state & MK_SHIFT)
        flags |= ui::EF_SHIFT_DOWN;
    if (key_state & MK_LBUTTON)
        flags |= ui::EF_LEFT_MOUSE_BUTTON;
    if (key_state & MK_MBUTTON)
        flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
    if (key_state & MK_RBUTTON)
        flags |= ui::EF_RIGHT_MOUSE_BUTTON;

    return flags;
}

} // namespace

namespace views {

DesktopDropTargetWin::DesktopDropTargetWin(aura::Window* root_window,
    HWND window)
    : ui::DropTargetWin(window)
    , root_window_(root_window)
    , target_window_(NULL)
{
}

DesktopDropTargetWin::~DesktopDropTargetWin()
{
    if (target_window_)
        target_window_->RemoveObserver(this);
}

DWORD DesktopDropTargetWin::OnDragEnter(IDataObject* data_object,
    DWORD key_state,
    POINT position,
    DWORD effect)
{
    scoped_ptr<OSExchangeData> data;
    scoped_ptr<ui::DropTargetEvent> event;
    DragDropDelegate* delegate;
    // Translate will call OnDragEntered.
    Translate(data_object, key_state, position, effect, &data, &event, &delegate);
    return ui::DragDropTypes::DragOperationToDropEffect(
        ui::DragDropTypes::DRAG_NONE);
}

DWORD DesktopDropTargetWin::OnDragOver(IDataObject* data_object,
    DWORD key_state,
    POINT position,
    DWORD effect)
{
    int drag_operation = ui::DragDropTypes::DRAG_NONE;
    scoped_ptr<OSExchangeData> data;
    scoped_ptr<ui::DropTargetEvent> event;
    DragDropDelegate* delegate;
    Translate(data_object, key_state, position, effect, &data, &event, &delegate);
    if (delegate)
        drag_operation = delegate->OnDragUpdated(*event);
    return ui::DragDropTypes::DragOperationToDropEffect(drag_operation);
}

void DesktopDropTargetWin::OnDragLeave(IDataObject* data_object)
{
    NotifyDragLeave();
}

DWORD DesktopDropTargetWin::OnDrop(IDataObject* data_object,
    DWORD key_state,
    POINT position,
    DWORD effect)
{
    int drag_operation = ui::DragDropTypes::DRAG_NONE;
    scoped_ptr<OSExchangeData> data;
    scoped_ptr<ui::DropTargetEvent> event;
    DragDropDelegate* delegate;
    Translate(data_object, key_state, position, effect, &data, &event, &delegate);
    if (delegate) {
        drag_operation = delegate->OnPerformDrop(*event);
        DragDropClient* client = aura::client::GetDragDropClient(root_window_);
        if (client && !client->IsDragDropInProgress() && drag_operation != ui::DragDropTypes::DRAG_NONE) {
            UMA_HISTOGRAM_COUNTS("Event.DragDrop.ExternalOriginDrop", 1);
        }
    }
    if (target_window_) {
        target_window_->RemoveObserver(this);
        target_window_ = NULL;
    }
    return ui::DragDropTypes::DragOperationToDropEffect(drag_operation);
}

void DesktopDropTargetWin::OnWindowDestroyed(aura::Window* window)
{
    DCHECK(window == target_window_);
    target_window_ = NULL;
}

void DesktopDropTargetWin::Translate(
    IDataObject* data_object,
    DWORD key_state,
    POINT position,
    DWORD effect,
    scoped_ptr<OSExchangeData>* data,
    scoped_ptr<ui::DropTargetEvent>* event,
    DragDropDelegate** delegate)
{
    gfx::Point location(position.x, position.y);
    gfx::Point root_location = location;
    root_window_->GetHost()->ConvertPointFromNativeScreen(
        &root_location);
    aura::Window* target_window = root_window_->GetEventHandlerForPoint(root_location);
    bool target_window_changed = false;
    if (target_window != target_window_) {
        if (target_window_)
            NotifyDragLeave();
        target_window_ = target_window;
        if (target_window_)
            target_window_->AddObserver(this);
        target_window_changed = true;
    }
    *delegate = NULL;
    if (!target_window_)
        return;
    *delegate = aura::client::GetDragDropDelegate(target_window_);
    if (!*delegate)
        return;

    data->reset(new OSExchangeData(new OSExchangeDataProviderWin(data_object)));
    location = root_location;
    aura::Window::ConvertPointToTarget(root_window_, target_window_, &location);
    event->reset(new ui::DropTargetEvent(
        *(data->get()),
        location,
        root_location,
        ui::DragDropTypes::DropEffectToDragOperation(effect)));
    (*event)->set_flags(ConvertKeyStateToAuraEventFlags(key_state));
    if (target_window_changed)
        (*delegate)->OnDragEntered(*event->get());
}

void DesktopDropTargetWin::NotifyDragLeave()
{
    if (!target_window_)
        return;
    DragDropDelegate* delegate = aura::client::GetDragDropDelegate(target_window_);
    if (delegate)
        delegate->OnDragExited();
    target_window_->RemoveObserver(this);
    target_window_ = NULL;
}

} // namespace views
